home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-09-02 | 20.4 KB | 503 lines | [TEXT/CCL2] |
- ;;;-*- Mode: Lisp; Package: WOOD -*-
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;
- ;; disk-cache.lisp
- ;; Copyright © 1992 Apple Computer, Inc. All rights reserved.
- ;; Code to support a cached byte I/O stream.
- ;;
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;
- ;; Modification History
- ;;
- ;; ------------- 0.5
- ;; 07/09/92 bill Don't extend the file until flushing a page requires it.
- ;; Keep a lock count, not just a bit.
- ;; 03/05/92 bill New file
- ;;
-
- ;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;
- ;; To do:
- ;;
- ;; without-interrupts in just the right places.
- ;; Add a journaling option.
- ;; Multi-user support.
- ;;
-
- (defpackage :wood)
- (in-package :wood)
-
- (export '(open-disk-cache close-disk-cache disk-cache-size
- get-disk-page mark-page-modified extend-disk-cache))
-
- ;;;;;;;;;;;;;
- ;;
- ;; (open-disk-cache filename &key shared-p page-size max-pages
- ;; if-exists if-does-not-exist)
- ;;
- ;; filename string or pathname
- ;; shared-p boolean. Open for shared I/O if specified and true.
- ;; page-size default: 512
- ;; max-pages default: 200
- ;; if-exists nil, :error, :supersede, or :overwrite.
- ;; Default: :overwrite
- ;; if-does-not-exist Same as for OPEN. default: :error.
- ;;
- ;; returns one value, a DISK-CACHE structure
-
- ;;;;;;;;;;;;;
- ;;
- ;; (close-disk-cache disk-cache)
- ;;
- ;; Flushes dirty pages and closes the stream for the given disk-cache.
-
- ;;;;;;;;;;;;;
- ;;
- ;; (disk-cache-size disk-cache)
- ;;
- ;; Return the number of bytes in the file
-
- ;;;;;;;;;;;;;
- ;;
- ;; (get-disk-page disk-cache address &optional modify-p)
- ;;
- ;; disk-cache DISK-CACHE structure, as returned from OPEN-DISK-CACHE.
- ;; address fixnum. the address from/to you wish to I/O
- ;; modify-p boolean. True if you plan to write. Default: nil.
- ;;
- ;; returns four values:
- ;; 1) array an array of type (array (signed-byte 8)) containing the byte
- ;; at address
- ;; 2) offset fixnum. The offset in the array for the byte at address.
- ;; 3) length fixnum. The number of bytes of valid data in array at offset.
- ;; Will be (- page-size (mod address page-size))
- ;; unless the page is the last one or later.
- ;; 4) page a disk-page structure that can be passed to mark-page-modified
-
- ;;;;;;;;;;;;;
- ;;
- ;; (mark-page-modified disk-page)
- ;;
- ;; disk-page DISK-PAGE structure as returned in the fourth value from
- ;; GET-DISK-PAGE.
- ;;
- ;; Sometimes you don't know in advance whether you'll modify a page.
- ;;
- ;; Returns true if the page was not already marked as modified, NIL
- ;; otherwise.
-
- ;;;;;;;;;;;;;
- ;;
- ;; (extend-disk-cache disk-cache new-size)
- ;;
- ;; new-size the new size of the file in bytes.
- ;; If smaller than the current size, this is a NOP.
-
-
- (defstruct (disk-cache (:print-function print-disk-cache))
- stream ; a stream to a file
- size ; the length of the file
- (page-size 512) ; size of a disk-page in bytes
- (mask -512) ; address mask
- page-count ; number of disk pages
- max-pages ; user's maximum
- page-hash ; page-address -> disk-page structure
- pages ; head of the disk-page chain
- dirty-pages ; head of the dirty page chain
- locked-pages ; head of locked pages chain
- log ; a LOG structure: see "recovery.lisp"
- write-hook ; hook to call when a page is written to disk
- file-eof ; current EOF on disk
- )
-
- (defun print-disk-cache (disk-cache stream level)
- (declare (ignore level))
- (print-unreadable-object (disk-cache stream :type t :identity t)
- (prin1 (pathname (disk-cache-stream disk-cache)) stream)))
-
- (defstruct (disk-page (:print-function print-disk-page))
- disk-cache ; back pointer
- stream ; the stream (did you guess?)
- address ; file address of base of this page
- (flags 0) ; bit 0 = dirty
- (size 0) ; actual size (smaller for last page)
- next ; next disk-page in the chain
- prev ; previous disk-page in the chain
- next-dirty ; next dirty page
- prev-dirty ; previous dirty page
- data ; an (unsigned-byte 8) array
- (lock-count 0)) ; non-zero means locked that many times.
-
- (defconstant $disk-page-flags_dirty-bit 0)
-
- (defun disk-page-dirty (disk-page)
- (logbitp $disk-page-flags_dirty-bit
- (the fixnum (disk-page-flags disk-page))))
-
- (defun (setf disk-page-dirty) (value disk-page)
- (setf (disk-page-flags disk-page)
- (if value
- (ccl::bitset $disk-page-flags_dirty-bit (disk-page-flags disk-page))
- (ccl::bitclr $disk-page-flags_dirty-bit (disk-page-flags disk-page))))
- (not (null value)))
-
- (defun disk-page-locked (disk-page)
- (let ((count (disk-page-lock-count disk-page)))
- (unless (eql 0 count)
- count)))
-
- (defun print-disk-page (disk-page stream level)
- (declare (ignore level))
- (print-unreadable-object (disk-page stream :type t :identity t)
- (format stream "~s~@{ ~s~}"
- (disk-page-address disk-page)
- (disk-page-size disk-page)
- (disk-page-dirty disk-page)
- (pathname (disk-page-stream disk-page)))))
-
- (defun cons-disk-page (disk-cache size)
- (make-disk-page :disk-cache disk-cache
- :stream (disk-cache-stream disk-cache)
- :data (make-array size :element-type '(unsigned-byte 8))))
-
- (defvar *open-disk-caches* nil)
-
- (defun open-disk-cache (filename &key shared-p (page-size 512) (max-pages 200)
- (if-exists :overwrite)
- (if-does-not-exist :error)
- (external-format :???? ef-p)
- write-hook)
- (let ((mask (lognot (1- (expt 2 (1- (integer-length page-size)))))))
- (unless (eql page-size (logand page-size mask))
- (error "page-size must be a power of 2"))
- (if (probe-file filename)
- (if (and ef-p (neq external-format (mac-file-type filename)))
- (error "(mac-file-type ~s) was ~s, should be ~s"
- filename (mac-file-type filename) external-format))
- (setq ef-p t))
- (let* ((ef (list :external-format external-format))
- (stream (apply #'open
- filename
- :direction (if shared-p :shared :io)
- :if-exists if-exists
- :if-does-not-exist if-does-not-exist
- (if ef-p ef))))
- (declare (dynamic-extent ef))
- (when stream
- (let* ((size (file-length stream))
- (disk-cache (make-disk-cache :stream stream
- :size size
- :file-eof size
- :page-size page-size
- :mask mask
- :max-pages max-pages
- :write-hook write-hook)))
- (multiple-value-bind (pages page-count)
- (make-linked-disk-pages
- disk-cache page-size max-pages (file-length stream))
- (setf (disk-cache-pages disk-cache) pages
- (disk-cache-page-count disk-cache) page-count
- (disk-cache-page-hash disk-cache) (make-hash-table :size page-count)))
- (push disk-cache *open-disk-caches*)
- disk-cache)))))
-
- (defun make-linked-disk-pages (disk-cache page-size page-count &optional file-length)
- (when file-length
- (setq page-count (max 1 (min page-count
- (floor (+ file-length page-size -1)
- page-size)))))
- (let (page last-page)
- (dotimes (i page-count)
- (let ((new-page (cons-disk-page disk-cache page-size)))
- (setf (disk-page-next new-page) page)
- (if page
- (setf (disk-page-prev page) new-page)
- (setq last-page new-page))
- (setq page new-page)))
- (setf (disk-page-next last-page) page
- (disk-page-prev page) last-page)
- (values page page-count)))
-
- (defun add-disk-pages (disk-cache count)
- (let* ((old-first-page (disk-cache-pages disk-cache))
- (new-first-page (make-linked-disk-pages
- disk-cache
- (disk-cache-page-size disk-cache)
- count)))
- (when old-first-page
- (let ((old-last-page (disk-page-prev old-first-page))
- (new-last-page (disk-page-prev new-first-page)))
- (setf (disk-page-next new-last-page) old-first-page
- (disk-page-prev old-first-page) new-last-page
- (disk-page-next old-last-page) new-first-page
- (disk-page-prev new-first-page) old-last-page)))
- (setf (disk-cache-pages disk-cache) new-first-page)
- (incf (disk-cache-page-count disk-cache) count)))
-
- (defun close-disk-cache (disk-cache)
- (flush-disk-cache disk-cache)
- (setq *open-disk-caches* (delq disk-cache *open-disk-caches* 1))
- (close (disk-cache-stream disk-cache))
- (setf (disk-cache-page-hash disk-cache) nil))
-
- (defun flush-disk-cache (disk-cache)
- (loop
- (let* ((page (disk-cache-dirty-pages disk-cache)))
- (unless page (return))
- (flush-disk-page page)))
- (finish-output (disk-cache-stream disk-cache)))
-
- (defun read-disk-page (disk-page address)
- (flush-disk-page disk-page)
- (setf (disk-page-address disk-page) address)
- (let* ((disk-cache (disk-page-disk-cache disk-page))
- (size (disk-cache-size disk-cache))
- (file-eof (disk-cache-file-eof disk-cache))
- (page-size (min (disk-cache-page-size disk-cache) (- size address))))
- (when (> file-eof address)
- (stream-read-bytes (disk-page-stream disk-page)
- address
- (disk-page-data disk-page)
- 0
- page-size))
- (setf (disk-page-size disk-page) page-size)))
-
- (defun flush-disk-page (disk-page)
- (let* ((disk-cache (disk-page-disk-cache disk-page))
- (write-hook (disk-cache-write-hook disk-cache)))
- (when (and write-hook (disk-page-dirty disk-page))
- (funcall write-hook disk-page))
- (when (disk-page-dirty disk-page) ; write-hook may have flushed this page
- (let* ((address (disk-page-address disk-page))
- (size (disk-page-size disk-page))
- (end-of-page (+ address size))
- (stream (disk-page-stream disk-page)))
- (when (> end-of-page (disk-cache-file-eof disk-cache))
- (set-minimum-file-length stream end-of-page)
- (setf (disk-cache-file-eof disk-cache) end-of-page))
- (stream-write-bytes stream
- address
- (disk-page-data disk-page)
- 0
- size))
- (let* ((next (disk-page-next-dirty disk-page))
- (prev (disk-page-prev-dirty disk-page)))
- (if (eq next disk-page)
- (setf next nil)
- (setf (disk-page-next-dirty prev) next
- (disk-page-prev-dirty next) prev))
- (setf (disk-page-next-dirty disk-page) nil
- (disk-page-prev-dirty disk-page) nil)
- (when (eq disk-page (disk-cache-dirty-pages disk-cache))
- (setf (disk-cache-dirty-pages disk-cache) next))))
- (setf (disk-page-dirty disk-page) nil)))
-
- ; This does least-recently-swapped for now.
- ; Could easily be modified to do least-recently-used, though
- ; that would slow it down a little.
- (defun get-disk-page (disk-cache address &optional modify-p)
- (let* ((hash (disk-cache-page-hash disk-cache))
- (base-address (logand address (disk-cache-mask disk-cache)))
- (page (gethash base-address hash))
- (offset (- address base-address))
- size)
- (block get-the-page
- (if page
- (setq size (disk-page-size page))
- (let ((max-size (disk-cache-size disk-cache)))
- (if (>= address max-size)
- (if (> address max-size)
- (error "~s > size of ~s" address disk-cache)
- (when (eql address base-address)
- ; If the address is the beginning of a page, and the end of
- ; the file, return a pointer off the end of the last page.
- (setq base-address (logand (1- address) (disk-cache-mask disk-cache))
- offset (- address base-address)
- page (gethash base-address hash))
- (when page
- (setq size (disk-page-size page))
- (return-from get-the-page)))))
- (setq page (disk-cache-pages disk-cache))
- (remhash (disk-page-address page) hash)
- ; Here's the least-recently-swapped part
- (setf (disk-cache-pages disk-cache) (disk-page-next page))
- ; There. That wasn't hard, was it?
- (setq size (read-disk-page page base-address))
- (setf (gethash base-address hash) page))))
- (when modify-p (mark-page-modified page))
- (values (disk-page-data page)
- offset
- (- size offset)
- page)))
-
- (defun mark-page-modified (disk-page)
- (unless (disk-page-dirty disk-page)
- ; Link this disk-page as the last one in the dirty cache.
- (let* ((disk-cache (disk-page-disk-cache disk-page))
- (dirty-pages (disk-cache-dirty-pages disk-cache)))
- (if dirty-pages
- (let ((prev-dirty (disk-page-prev-dirty dirty-pages)))
- (setf (disk-page-next-dirty prev-dirty) disk-page
- (disk-page-prev-dirty disk-page) prev-dirty
- (disk-page-next-dirty disk-page) dirty-pages
- (disk-page-prev-dirty dirty-pages) disk-page))
- (setf (disk-page-next-dirty disk-page) disk-page
- (disk-page-prev-dirty disk-page) disk-page
- (disk-cache-dirty-pages disk-cache) disk-page)))
- (setf (disk-page-dirty disk-page) t)))
-
- ; Return the lock count after locking.
- (defun lock-page (disk-page)
- (let ((lock-count (disk-page-lock-count disk-page)))
- (declare (fixnum lock-count))
- (when (eql 0 lock-count)
- (let* ((disk-cache (disk-page-disk-cache disk-page))
- (prev (disk-page-prev disk-page))
- (next (disk-page-next disk-page))
- (locked (disk-cache-locked-pages disk-cache))
- (prev-locked (if locked (disk-page-prev locked) disk-page)))
- (when (null locked)
- (setf (disk-cache-locked-pages disk-cache) (setq locked disk-page)))
- (setf (disk-page-next prev) next
- (disk-page-prev next) prev
- (disk-page-next prev-locked) disk-page
- (disk-page-prev disk-page) prev-locked
- (disk-page-prev locked) disk-page
- (disk-page-next disk-page) locked)
- (when (eq disk-page (disk-cache-pages disk-cache))
- (setf (disk-cache-pages disk-cache)
- (if (eq next disk-page) nil next)))))
- (setf (disk-page-lock-count disk-page)
- (the fixnum (1+ lock-count)))))
-
- ; Return the lock count or NIL if the page unlocked when this returns.
- (defun unlock-page (disk-page)
- (let ((count (disk-page-lock-count disk-page)))
- (declare (fixnum count))
- (when (not (eql 0 count))
- (progn
- (when (eql count 1)
- (let* ((disk-cache (disk-page-disk-cache disk-page))
- (prev-locked (disk-page-prev disk-page))
- (next-locked (disk-page-next disk-page))
- (pages (disk-cache-pages disk-cache))
- (prev (if pages (disk-page-prev pages) disk-page)))
- (when (null pages)
- (setf (disk-cache-pages disk-cache) (setq pages disk-page)))
- (setf (disk-page-next prev-locked) next-locked
- (disk-page-prev next-locked) prev-locked
- (disk-page-next prev) disk-page
- (disk-page-prev disk-page) prev
- (disk-page-prev pages) disk-page
- (disk-page-next disk-page) pages)
- (when (eq disk-page (disk-cache-locked-pages disk-cache))
- (setf (disk-cache-locked-pages disk-cache)
- (if (eq next-locked disk-page) nil next-locked)))))
- (setf (disk-page-lock-count disk-page) (decf count))
- (and (not (eql 0 count)) count)))))
-
-
- (defmacro with-locked-page ((disk-page-or-disk-cache
- &optional address modify-p array offset length page)
- &body body &environment env)
- (if address
- (let (ignored-params)
- (multiple-value-bind (body-tail decls) (ccl::parse-body body env nil)
- (flet ((normalize (param &optional (ignoreable? t))
- (or param
- (let ((res (gensym)))
- (if ignoreable? (push res ignored-params))
- res))))
- `(multiple-value-bind (,(normalize array) ,(normalize offset)
- ,(normalize length) ,(setq page (normalize page nil)))
- (get-disk-page ,disk-page-or-disk-cache ,address
- ,@(if modify-p `(,modify-p)))
- ,@(when ignored-params
- `((declare (ignore ,@ignored-params))))
- ,@decls
- (with-locked-page (,page)
- ,@body-tail)))))
- (let ((page-var (gensym)))
- `(let ((,page-var ,disk-page-or-disk-cache))
- (unwind-protect
- (progn
- (lock-page ,page-var)
- ,@body)
- (unlock-page ,page-var))))))
-
- (defun lock-page-at-address (disk-cache address)
- (let ((page (nth-value 3 (get-disk-page disk-cache address))))
- (values (lock-page page) page)))
-
- (defun extend-disk-cache (disk-cache new-size)
- (let ((size (disk-cache-size disk-cache)))
- (when (> new-size size)
- ; Update size of last page
- (let* ((page-address (logand (1- size) (disk-cache-mask disk-cache)))
- (page (gethash page-address (disk-cache-page-hash disk-cache))))
- (when page
- (setf (disk-page-size page)
- (min (length (disk-page-data page)) (- new-size page-address)))))
- ; Add some new pages, if not maxed out already
- (let* ((page-size (disk-cache-page-size disk-cache))
- (page-count (min (disk-cache-max-pages disk-cache)
- (floor (+ new-size page-size -1) page-size))))
- (when (> (decf page-count (disk-cache-page-count disk-cache)) 0)
- (add-disk-pages disk-cache page-count)))
- ; increase the file size & install the new size
- (setf (disk-cache-size disk-cache) new-size))))
-
- (defun flush-all-disk-caches ()
- (dolist (dc *open-disk-caches*)
- (if (eq :closed (stream-direction (disk-cache-stream dc)))
- (setq *open-disk-caches* (delq dc *open-disk-caches*))
- (flush-disk-cache dc))))
-
- (pushnew 'flush-all-disk-caches *lisp-cleanup-functions*)
-
- #|
- (setq dc (open-disk-cache "temp.lisp"))
-
- ; read a string from dc
- (defun rc (address size)
- (declare (optimize (debug 3)))
- (declare (special dc))
- (let ((file-size (disk-cache-size dc)))
- (setq size (max 0 (min size (- file-size address)))))
- (let ((string (make-string size))
- (index 0))
- (loop
- (when (<= size 0) (return string))
- (multiple-value-bind (array array-index bytes) (get-disk-page dc address)
- (dotimes (i (min size bytes))
- (setf (schar string index) (code-char (aref array array-index)))
- (incf index)
- (incf array-index))
- (decf size bytes)
- (incf address bytes)))))
-
- ; write a string to dc
- (defun wc (string address)
- (declare (special dc))
- (let* ((length (length string))
- (min-size (+ address length))
- (index 0))
- (when (> min-size (disk-cache-size dc))
- (extend-disk-cache dc min-size))
- (loop
- (when (<= length 0) (return))
- (multiple-value-bind (array array-index bytes) (get-disk-page dc address t)
- (dotimes (i (min length bytes))
- (declare (type (array (unsigned-byte 8)) array))
- (setf (aref array array-index) (char-code (schar string index)))
- (incf index)
- (incf array-index))
- (incf address bytes)
- (decf length bytes)))))
-
- (close-disk-cache dc)
-
- |#
-